/*
 * @(#)TelnetDaemon.java	1.0 Apr 26, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.net.telnet;

import org.kohsuke.args4j.*;
import org.eoti.net.PortMap;
import org.eoti.net.PortConfiguration;
import org.eoti.io.args4j.Args4jRegistry;
import org.eoti.io.args4j.CommandLineApp;

import java.util.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.IOException;

public class TelnetDaemon
extends CommandLineApp
{
	@Option(name="-port", usage="port to serve[Examples: '80', 'tcp/80', 'telnet']")
	protected PortConfiguration port = PortMap.WellKnownPort.telnet.getConfig();

	@Option(name="-debug", usage="show debugging information")
	protected boolean debug = false;

	@Option(name="-verbose", usage="verbose logging")
	protected boolean verbose = false;

	public static void main(String[] args)
	{
		TelnetDaemon telnetd = new TelnetDaemon(args);
	}

	protected ServerSocket server;
	protected Socket client;
	protected OutputStream toClient;
	protected InputStream fromClient;

	public TelnetDaemon(String[] args)
	{
		super(args);
		try{
			server = new ServerSocket(port.getPort());
			client = server.accept();
			toClient = client.getOutputStream();
			fromClient = client.getInputStream();
			toClient.write("Welcome to telnetd.\r\n".getBytes());
			int read = -2;
			while((read = fromClient.read()) != -1)
			{
				if(read == TelnetCommand.InterpretAsCommand.getValue())
				{
					System.out.println();
					debug(TelnetCommand.InterpretAsCommand);
					readIAC();
					continue;
				}

				System.out.print((char)read);
			}
		} catch(Exception e){
			System.err.format("Error: %s\n", e.getMessage());
			e.printStackTrace();
		}
	}

	protected void debug(TelnetCommand command){System.out.format("<%s>",command);}
	protected void debug(TelnetOption option){System.out.format("<%s>",option);}

	protected TelnetCommand currentCommand = null;
	protected TelnetOption currentOption = null;
	protected String currentValue = null;

	protected void readIAC()
			throws IOException
	{
		int read = fromClient.read();
		for(TelnetCommand command : TelnetCommand.values())
		{
			if(read != command.getValue())
				continue;

			debug(command);
			currentCommand = command;
			if(command == TelnetCommand.SubnegotiationBegin)
			{
				readSubnegotiation();
			}else{
				readOption();
				handleCurrentCommand();
			}

			break;
		}
		System.out.println();
	}

	protected void readSubnegotiation()
			throws IOException
	{
		readOption();
		int read = fromClient.read();
		if(read == TelnetCommand.OptionRequired.getValue())
		{
			read = fromClient.read();
			if(read != TelnetCommand.InterpretAsCommand.getValue())
				debug(read);

			read = fromClient.read();
			if(read != TelnetCommand.SubnegotiationEnd.getValue())
				debug(read);
		}else if(read == TelnetCommand.OptionSupplied.getValue()){
			while( (read = fromClient.read()) != TelnetCommand.InterpretAsCommand.getValue())
			{
				if(currentValue == null) currentValue = "";
				else currentValue = currentValue + (char)read;

				System.out.print((char)read);
			}
			read = fromClient.read();
			if(read != TelnetCommand.SubnegotiationEnd.getValue())
				debug(read);
		}else{
			debug(read);
		}
	}

	protected void debug(int read)
	{
		System.out.format("<[ERROR: %c]>", (char)read);
	}

	protected void readOption()
			throws IOException
	{
		int read = fromClient.read();
		for(TelnetOption option : TelnetOption.values())
		{
			if(read != option.getValue())
				continue;

			debug(option);
			currentOption = option;
			break;
		}
	}

	protected void handleCurrentCommand()
			throws IOException
	{
		if(currentCommand == null) return;
		if(currentOption == null) return;

		switch(currentCommand)
		{
			case WILL:
				toClient.write(TelnetCommand.InterpretAsCommand.getValue());
				toClient.write(TelnetCommand.DO.getValue());
				toClient.write(currentOption.getValue());

				if(currentOption == TelnetOption.TerminalType)
				{
					toClient.write(TelnetCommand.InterpretAsCommand.getValue());
					toClient.write(TelnetCommand.SubnegotiationBegin.getValue());
					toClient.write(currentOption.getValue());
					toClient.write(TelnetCommand.OptionRequired.getValue());
					toClient.write(TelnetCommand.InterpretAsCommand.getValue());
					toClient.write(TelnetCommand.SubnegotiationEnd.getValue());
				}
				break;
			case DO:
				toClient.write(TelnetCommand.InterpretAsCommand.getValue());
				toClient.write(TelnetCommand.DO.getValue());
				toClient.write(currentOption.getValue());
				break;
			default:
				break;
		}
	}
}
